using System;
using System.Diagnostics;
using System.Collections;

namespace CNeural
{

	public class NNSimple 
	{

		double learningRate = 0.1F;  // Learning rate
		double momentum= 0.9F;     // Momentum parameter
		int numberOfLearningCycles = 1000;


		private int numberNodesInput ;
		private int numberNodesHidden ;
		private int numberNodesOutput ;
		private int numberNNPatterns ;

		private System.Random randomClass;
		
		ArrayList patterns ;

		neuron_type[] inputLayer ;
		neuron_type[] hiddenLayer ;
		neuron_type[] outputLayer ;
		double[] test_pat ;
		double[] desired ;


		public NNSimple()
		{
			randomClass = new System.Random(4);
			
		}
	
		#region properties

		public int NumberNodeInput
		{
			get
			{
				return this.numberNodesInput;
			}
			set
			{
				numberNodesInput = value;
			}
		}
		
		public int NumberNodesHidden
		{
			get
			{
				return this.numberNodesHidden;
			}
			set
			{
				numberNodesHidden = value;
			}
		}
		
		public int NumberNodesOutput
		{
			get
			{
				return this.numberNodesOutput;
			}
			set
			{
				numberNodesOutput = value;
			}
		}
		
		public int NumberPatterns
		{
			get
			{
				return this.numberNNPatterns;
			}
			set
			{
				numberNNPatterns = value;
			}
		}
		
	

		#endregion

		public void Test()
		{
			test_pat = new double[this.numberNodesInput];
			desired = new double[numberNodesOutput];
			// Input layer
			inputLayer = new neuron_type[this.numberNodesInput ];   
			for(int i = 0; i < this.numberNodesInput ; i++)
			{
				inputLayer[i] = new neuron_type(this.numberNodesInput );
			}
			hiddenLayer = new neuron_type[numberNodesHidden];   // Hidden layer
			for(int i = 0; i < numberNodesHidden; i++)
			{
				hiddenLayer[i] = new neuron_type(this.numberNodesInput );
			}
			outputLayer = new neuron_type[numberNodesOutput];   // Output layer
			for(int i = 0; i < numberNodesOutput; i++)
			{
				outputLayer[i] = new neuron_type(this.numberNodesHidden);
			}
			
			SetNNPatterns();

			//			double[] INP_PATTERNS = { {0,0,0,0,0}, {0,0,0,0,1},{0,0,0,1,0},
			//				{0,0,1,0,0},{0,1,0,0,0}};
			//
			//			double OUT_PATTERNS =  {{0,0,0},{0,0,0},{0,0,1},{0,1,0},{0,1,1}};
			//									

			//randomize();           // Set the random number generator
			random_weights();
			Debug.WriteLine("Start");
			Debug.WriteLine("To start with, you should type in a few test patterns to see"
				+ "how the network performs before it is trained." );
			test_the_network();
			train_the_network();
			
			Debug.WriteLine("Now you should type in a few more test patterns to see how the"
				+ "network performs after it has been trained." );
			test_the_network();
		}

		/* These arrays contain the training input patterns and the corresponding
		output patterns. Here they form a binary encoder, changing 0010 into
		01 and 1000 into 11 etc. but feel free to change them into anything you
		want. That is why they are arrays of double values rather than integers.
		 */

		//There is one dummy pattern at the start of each of these as the indices
		//start from 0, but I start counting from 1
	

		// All neurons are defined having numberNodesInput weights from the previous layer
		// (although not all those will be used in every neuron), an output value
		// and a threshold
		class neuron_type
		{
			public neuron_type(int numberNodesInput)
			{
				Weights = new double[numberNodesInput  ];//[this.numberNodesInput  ], ;  // w = the weights themselves
				WeightChanges = new double[numberNodesInput  ];
			}
			public double[] Weights ;//= new double[this.numberNodesInput  ];//[this.numberNodesInput  ], ;  // w = the weights themselves
			public double[] WeightChanges ;//= new double[this.numberNodesInput  ];
			// change = change in weights (used in
			// training only!
			public double Threshold;
			public double Result;
			public double t_change;        // Change in threshold (used in training only)
			public double Error;               // Error for this node (used in training only)
		};

		//An array to hold any input patterns used for training or typed in
	

		private void SetNNPatterns()
		{
			ArrayList inp;
			ArrayList outp ;
			/// create a trainign set to learn the xor problem
			this.patterns = new ArrayList();

			inp = new ArrayList();
			inp.Add(0.0);
			inp.Add(0.0);
			outp = new ArrayList();
			outp.Add(0.0);
			this.patterns.Add(new NNPattern(inp,outp));

			inp = new ArrayList();
			inp.Add(0.0);
			inp.Add(1.0);
			outp = new ArrayList();
			outp.Add(1.0);
			this.patterns.Add(new NNPattern(inp,outp));

			inp = new ArrayList();
			inp.Add(1.0);
			inp.Add(0.0);
			outp = new ArrayList();
			outp.Add(1.0);
			this.patterns.Add(new NNPattern(inp,outp));

			inp = new ArrayList();
			inp.Add(1.0);
			inp.Add(1.0);
			outp = new ArrayList();
			outp.Add(1.0);
			this.patterns.Add(new NNPattern(inp,outp));


		}
	

//		// Ask the user whether to continue or not. Returns 1=true, 0=false
//		// as some C++ compilers don't have a boolean type
//		int contin ()
//		{
//			char k;
//			cout << endl << " Do you want another test pattern? (Press Y or N) ";
//			do
//			{  cin >> k;  }
//			while (k != 'Y' && k != 'y' && k != 'N' && k != 'n');
//			if (k == 'Y' || k == 'y')
//				return 1;
//			else
//				return 0;
//		}

//		// Ask the user to type a pattern which is to be used for testing
//		void get_test_pattern ()
//		{
//			cout << endl;
//			cout << "Please enter the values for the test pattern." << endl;
//			cout << "You should type in " << numberNodesInput <<
//				" values (press Enter after each one)." << endl;
//			for (int i = 0; i < numberNodesInput; i++)
//			{
//					cout << "    Please enter value " << i << " : ";
//				cin >> test_pat[i];
//			}
//		}

		// THE FOLLOWING ARE FOR RUNNING THE NETWORK - FORWARD PROPAGATION

		double sigmoid (double x)
		{
			if (System.Math.Abs(x) < 38)              // Handle possible overflow
			   return 1/(1+ (double)System.Math.Exp(-x));        // exp only valid between -39 and 38
		   else
			   if (x >= 38)
			   return 1;
		   else
			   return 0;
		}

		void run_input_layer ()
		{
			double sum;
			for (int i = 0; i < numberNodesInput; i++)
			{
					sum = 0;
				for (int j = 0; j < numberNodesInput; j++)
					sum += inputLayer[i].Weights[j] * test_pat[j];
				inputLayer[i].Result = sigmoid(sum - inputLayer[i].Threshold);
			}
		}

		void run_hidden_layer ()
		{
			double sum;
			for (int i = 0; i < numberNodesHidden; i++)
			{
				sum = 0;
				for (int j = 0; j < numberNodesInput; j++)
					sum += hiddenLayer[i].Weights[j] * inputLayer[j].Result;
				hiddenLayer[i].Result = sigmoid(sum - hiddenLayer[i].Threshold);
			}
		}

		void run_output_layer ()
		{
			double sum;
			for (int i = 0; i < numberNodesOutput; i++)
			{
				sum = 0;
				for (int j = 0; j < numberNodesHidden; j++)
					sum += outputLayer[i].Weights[j] * hiddenLayer[j].Result;
				outputLayer[i].Result = sigmoid(sum - outputLayer[i].Threshold);
			}
		}

		void run_the_network ()
		{
			run_input_layer();
			run_hidden_layer();
			run_output_layer();
		}

		// This procedure displays the results of the test on the screen.
		void display_the_results ()
		{
			Debug.WriteLine("Inputs: ");

			for (int i = 0; i < numberNodesInput; i++)
				Debug.WriteLine(test_pat[i].ToString());
			Debug.WriteLine("Desired :       Outputs: ");
			for (int i = 0; i < numberNodesOutput; i++)      // 'i' already declared
				Debug.WriteLine(desired[i].ToString() + " ; " + outputLayer[i].Result.ToString());
			
		}

		void test_the_network ()
		{
			Debug.WriteLine("I will ask you for test patterns. At the end of each test "
				+ "you will be asked if you want to do another test." );
			
			//set the test pattern
			for(int pat = 0; pat < this.patterns.Count; pat++)
			{
				NNPattern patt = this.patterns[pat] as NNPattern;
				for (int i = 0; i < numberNodesInput; i++)
				{
	
					
					test_pat[i] = patt.InputValue(i);

				}
				run_the_network();
				display_the_results();
			}//			{
				//					cout << "    Please enter value " << i << " : ";
				//				cin >> test_pat[i];
				//			}


				
			
			//while (contin() == 1);     // Function contin returns 1 while user
		}                          // wants to continue

		// THE FOLLOWING PROCEDURES ARE FOR TRAINING THE NETWORK

		void calculate_output_layer_errors ()
		{
			for (int j = 0; j < numberNodesOutput; j++)
			{
				double dval =   (desired[j] - outputLayer[j].Result) * outputLayer[j].Result * (1 - outputLayer[j].Result);
				outputLayer[j].Error = dval;
			}
		}

		void calculate_hidden_layer_errors ()
		{
				double sum;
			for (int i = 0; i < numberNodesHidden; i++)
			{
					sum = 0;             // Find sum of error products for output layer
				for (int j = 0; j < numberNodesOutput; j++)
					sum += outputLayer[j].Error * outputLayer[j].Weights[i];
				hiddenLayer[i].Error = hiddenLayer[i].Result * (1 - hiddenLayer[i].Result) * sum;
			}
		}

		void calculate_input_layer_errors ()
		{
				double sum;
			for (int i = 0; i < numberNodesInput; i++)
			{
					sum = 0;
				for (int j = 0; j < numberNodesHidden; j++)
					sum += hiddenLayer[j].Error * hiddenLayer[j].Weights[i];
				inputLayer[i].Error = inputLayer[i].Result * (1 - inputLayer[i].Result) * sum;
			}
		}

		/* You will notice that the lines changing the threshold values have been
		 bracketed out in the following procedure - they should be there according
		 to theory, but if I include them, the network doesn't produce the correct
		 values. If I miss them out, as I have now, it produces correct values.
		 If anyone can throw any light on this, I would be most grateful! */

		void weight_change ()
		{
			

			// First tackle weights from hidden layer to output layer
			// 'i' refers to a node in hidden layer, j to a node in output layer
			for (int j = 0; j < numberNodesOutput; j++)  // Go through all output nodes
			{
					for (int i = 0; i < numberNodesHidden; i++)  // Adapt all weights
			  {
					  outputLayer[j].WeightChanges[i] = this.learningRate * outputLayer[j].Error * hiddenLayer[i].Result + this.momentum* outputLayer[j].WeightChanges[i];
				  // This is the previous value -------^
				  outputLayer[j].Weights[i] += outputLayer[j].WeightChanges[i];
				  // Now adapt threshold as if from a node with activation 1
				  outputLayer[j].t_change = this.learningRate * outputLayer[j].Error * 1 + this.momentum* outputLayer[j].t_change;
				  //outputLayer[j].Threshold += outputLayer[j].t_change;
			  }
			}

			// Now tackle weights from input layer to hidden layer
			// i refers to a node in input layer, j refers to node in hidden layer
			for (int j = 0; j < numberNodesHidden; j++)
			{
					for (int i = 0; i < numberNodesInput; i++)
			  {
					  hiddenLayer[j].WeightChanges[i] = this.learningRate * hiddenLayer[j].Error * inputLayer[i].Result + this.momentum* hiddenLayer[j].WeightChanges[i];
				  // This is the previous value ----^
				  hiddenLayer[j].Weights[i] += hiddenLayer[j].WeightChanges[i];
			  }
				// Now adpapt threshold as if from a node with activation 1
				hiddenLayer[j].t_change = this.learningRate * hiddenLayer[j].Error * 1 + this.momentum* hiddenLayer[j].t_change;
				//hiddenLayer[j].Threshold += hiddenLayer[j].t_change;
			}

			// Now tackle weights from input to net to input layer
			// i refers to a pattern input, j refers to node in input layer
			for (int j = 0; j < numberNodesInput; j++)    // go through all input layer nodes
			{
					for (int i = 0; i < numberNodesInput; i++) // Adapt all weights
			  {
					  inputLayer[j].WeightChanges[i] = this.learningRate*inputLayer[j].Error*test_pat[i] + this.momentum *inputLayer[j].WeightChanges[i];
				  // This is the previous value ------^
				  inputLayer[j].Weights[i] += inputLayer[j].WeightChanges[i];
			  }
				// Now adapt threshold as if from a node with activation 1
				inputLayer[j].t_change = this.learningRate * inputLayer[j].Error * 1 + this.momentum* inputLayer[j].t_change;
				//inputLayer[j].Threshold += inputLayer[j].t_change;
			}
		}

		// Perform back propagation on the network}
		void back_propagate ()
		{
			calculate_output_layer_errors();
			calculate_hidden_layer_errors();
			calculate_input_layer_errors();
			weight_change();
		}

		// Get a random number in the range 0 to 1 as a double
		double r ()
		{ 
			//return (rand() + 0.0)/RAND_MAX;  
			double d = (double) randomClass.NextDouble() ;

			return d;
		}

		//Set the weights and thresholds for all the nodes to small random values
		// in the range 0 to 1
		void random_weights ()
		{
			for (int i = 0; i < numberNodesInput; i++)
			{
				for (int j = 0; j < numberNodesInput; j++)
				{
					inputLayer[i].Weights[j] = r();
					//inputLayer[i].Weights[j] = 0.5;

				}
				inputLayer[i].Threshold = r();
			}
			for (int i = 0; i < numberNodesHidden; i++)
			{
				for (int j = 0; j < numberNodesInput; j++)
				{
					 hiddenLayer[i].Weights[j] = r();
					 //hiddenLayer[i].Weights[j] = 0.5;
				}
				 
				hiddenLayer[i].Threshold = r();
			}
			for (int i = 0; i < numberNodesOutput; i++)
			{
				for (int j = 0; j < numberNodesHidden; j++)
				{
					outputLayer[i].Weights[j] = r();
					//outputLayer[i].Weights[j] = 0.5;
				}
				outputLayer[i].Threshold = r();
			}
		}

		// At the start of back propagation, there are no weight changes to
		// influence the next cycle, so clear the arrays
		void blank_changes ()
		{
			for (int j = 0; j < numberNodesInput; j++)
		  {
				  for (int i = 0; i < numberNodesInput; i++)
				inputLayer[j].WeightChanges[i] = 0;
			  inputLayer[j].t_change = 0;
		  }
			for (int j = 0; j < numberNodesHidden; j++)
			{
					for (int i = 0; i < numberNodesInput; i++)
				  hiddenLayer[j].WeightChanges[i] = 0;
				hiddenLayer[j].t_change = 0;
			}
			for (int j = 0; j < numberNodesOutput; j++)
			{
					for (int i = 0; i < numberNodesHidden; i++)
				  outputLayer[j].WeightChanges[i] = 0;
				outputLayer[j].t_change = 0;
			}
		}

		void train_the_network ()
		{
			
			//cout << endl;
			//cout << "Enter the number of training cycles (typically 100) : ";
			
			blank_changes();              // Clear all "previous" weight changes
			for (long loop = 0; loop < numberOfLearningCycles; loop++)
				for (int pat = 0; pat < numberNNPatterns; pat++) // Cycle through all patterns
				{
					for (int i = 0; i < numberNodesInput; i++)    // Copies input pattern into
					{
						NNPattern patt = this.patterns[pat] as NNPattern;
						test_pat[i] = patt.InputValue(i);  // 'test_pat' array
					}
					for (int i = 0; i < numberNodesOutput; i++)        // Copies output pattern into
					{
						NNPattern patt = this.patterns[pat] as NNPattern;
						desired[i] = Convert.ToDouble(patt.ArrayOutputDesired[i]);
						//desired[i] = OUT_PATTERNS[pat][i];   // 'desired' array
					}
					run_the_network();
					back_propagate();
				}
		}

	
	}
//
}
